﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics.Contracts;
using System.Reflection;
using System.Linq.Expressions;

namespace Isuka.Reflection
{
    public static class MemberHelper
    {
        static Dictionary<Type, PropertyInfoDictionary> pidCache
            = new Dictionary<Type, PropertyInfoDictionary>();

        public static IEnumerable<TAttribute>
            GetAttribute<TAttribute>(this MemberInfo info, bool inherit = false)
            where TAttribute : Attribute
        {
            Contract.Requires<ArgumentNullException>(info != null, "info");
            Contract.Ensures(Contract.Result<IEnumerable<TAttribute>>() != null);

            return info.GetCustomAttributes(typeof(TAttribute), inherit).Select(x => (TAttribute)x);
        }

        /// <summary>
        /// ラムダ式からメンバー情報を取得する
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="expr"></param>
        /// <returns></returns>
        public static MemberInfo GetMemberInfo<T>(Expression<Func<T>> expr)
        {
            Contract.Requires<ArgumentNullException>(expr != null, "expr");
            return expr.GetMemberInfoCore();
        }

        /// <summary>
        /// 式ノードから、プロパティ情報を得るための仕組み。
        /// </summary>
        /// <param name="except">式ノード</param>
        /// <returns>プロパティ情報を得る</returns>
        static MemberInfo GetMemberInfoCore(this Expression expr)
        {
            if (expr == null)
                return null;

            switch (expr.NodeType)
            {
                case ExpressionType.Lambda:
                    return ((LambdaExpression)expr).Body.GetMemberInfoCore();
                case ExpressionType.Quote:
                    return ((UnaryExpression)expr).Operand.GetMemberInfoCore();
                case ExpressionType.MemberAccess:
                    return ((MemberExpression)expr).Member as MemberInfo;
                default:
                    return null;
            }
        }

        /// <summary>
        /// 指定した型の指定した名前のプロパティ情報を取得する。
        /// 存在しない場合はnullを返す
        /// </summary>
        /// <param name="type"></param>
        /// <param name="propertyName"></param>
        /// <returns></returns>
        public static PropertyInfo GetPropertyInfo(Type type, string propertyName)
        {
            Contract.Requires<ArgumentNullException>(type != null, "type");
            Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(propertyName));

            var pd = GetPropertyInfoDictionary(type);
            return pd.Contains(propertyName) ? pd[propertyName] : null;
        }


        /// <summary>
        /// 指定した型のパブリックプロパティ一覧を取得する
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static PropertyInfoDictionary GetPropertyInfoDictionary(Type type)
        {
            Contract.Requires<ArgumentNullException>(type != null, "type");
            Contract.Ensures(Contract.Result<PropertyInfoDictionary>() != null);

            if (pidCache.ContainsKey(type))
            {
                var r = pidCache[type];
                Contract.Assume(r != null);
                return r;
            }
            else
            {
                var r = new PropertyInfoDictionary(type);
                pidCache.Add(type, r);
                return r;
            }
        }
    }
}
